﻿using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Text;

using Storm.TextEditor.Editor;
using Storm.TextEditor.Editor.Text;
using Storm.TextEditor.Editor.Undo;
using Storm.TextEditor.Parser.Objects.Collections;
using System;

namespace Storm.TextEditor.Parser.Objects
{
    /// <summary>
    /// Represents a row in a document.
    /// </summary>
    public sealed class Row
        : IEnumerable
    {
        #region Fields

        private string text = "";
        private WordCollection words = new WordCollection();

        private WordCollection formattedWords = new WordCollection();
        private SegmentCollection startSegments = new SegmentCollection();
        private SegmentCollection endSegments = new SegmentCollection();
        private SyntaxDocument document = null;
        private ImageIndexCollection images = new ImageIndexCollection();

        private Segment startSegment = null;
        private Segment endSegment = null;

        private RowState rowState = Objects.RowState.NotParsed;
        private Color backColor = Color.Transparent;

        private object tag = null;
        private bool inQueue = false;
        private bool inKeywordQueue = false;
        private int indent = 0;

        private bool hovered = false;
        private bool bookmarked = false;
        private bool breakpoint = false;
        private bool mayHaveFoldMarker = true;

        private Segment expansionStartSegment = null;
        private Segment expansionEndSegment = null;

        private int expansionPixelStart = 0;
        private int expansionStartChar = 0;
        private int expansionPixelEnd = 0;
        private int expansionEndChar = 0;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the expansion ending character of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the expansion ending character of the Row.")]
        public int ExpansionEndChar
        {
            get { return expansionEndChar; }
            set { expansionEndChar = value; }
        }

        /// <summary>
        /// Gets or sets the expansion pixel end of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the expansion pixel end of the Row.")]
        public int ExpansionPixelEnd
        {
            get { return expansionPixelEnd; }
            set { expansionPixelEnd = value; }
        }

        /// <summary>
        /// Gets or sets the expansion starting character of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the expansion starting character of the Row.")]
        public int ExpansionStartChar
        {
            get { return expansionStartChar; }
            set { expansionStartChar = value; }
        }

        /// <summary>
        /// Gets or sets the expansion pixel start of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the expansion pixel start of the Row.")]
        public int ExpansionPixelStart
        {
            get { return expansionPixelStart; }
            set { expansionPixelStart = value; }
        }

        /// <summary>
        /// Gets or sets the level of indentation the Row is currently on.
        /// </summary>
        [Browsable(false)]
        [Category("Appearance")]
        [Description("Gets or sets the level of indentation the Row is currently on.")]
        public int Indent
        {
            get { return indent; }
            set { indent = value; }
        }

        /// <summary>
        /// Gets or sets whether the Row is currently in its parent document's keyword parsing queue.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets whether the Row is currently in its parent document's keyword parsing queue.")]
        public bool InKeywordQueue
        {
            get { return inKeywordQueue; }
            set { inKeywordQueue = value; }
        }

        /// <summary>
        /// Gets or sets whether the Row is currently in its parent document's parsing queue.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets whether the Row is currently in its parent document's parsing queue.")]
        public bool InQueue
        {
            get { return inQueue; }
            set { inQueue = value; }
        }

        /// <summary>
        /// Gets or sets the Row's tag.
        /// </summary>
        [Browsable(true)]
        [Category("Data")]
        [Description("Gets or sets the Row's tag.")]
        public object Tag
        {
            get { return tag; }
            set { tag = value; }
        }

        /// <summary>
        /// Gets or sets the expansion end segment of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the expansion end segment of the Row.")]
        public Segment ExpansionEndSegment
        {
            get { return expansionEndSegment; }
            set { expansionEndSegment = value; }
        }

        /// <summary>
        /// Gets or sets the expansion start segment of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the expansion start segment of the Row.")]
        public Segment ExpansionStartSegment
        {
            get { return expansionStartSegment; }
            set { expansionStartSegment = value; }
        }

        /// <summary>
        /// Gets or sets the end segment of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the end segment of the Row.")]
        public Segment EndSegment
        {
            get { return endSegment; }
            set { endSegment = value; }
        }

        /// <summary>
        /// Gets or sets the start segment of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the start segment of the Row.")]
        public Segment StartSegment
        {
            get { return startSegment; }
            set { startSegment = value; }
        }

        /// <summary>
        /// Gets or sets the images of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Appearance")]
        [Description("Gets or sets the images of the Row.")]
        public ImageIndexCollection Images
        {
            get { return images; }
            set { images = value; }
        }

        /// <summary>
        /// Gets or sets the parent document of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the parent document of the Row.")]
        public SyntaxDocument Document
        {
            get { return document; }
            set { document = value; }
        }

        /// <summary>
        /// Gets or sets the collection of segments that contains the ending segments of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the collection of segments that contains the ending segments of the Row.")]
        public SegmentCollection EndSegments
        {
            get { return endSegments; }
            set { endSegments = value; }
        }

        /// <summary>
        /// Gets or sets the collection of segments that contains the starting segments of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the collection of segments that contains the starting segments of the Row.")]
        public SegmentCollection StartSegments
        {
            get { return startSegments; }
            set { startSegments = value; }
        }

        /// <summary>
        /// Gets or sets the words of the Row that are formatted.
        /// </summary>
        [Browsable(false)]
        [Category("Appearance")]
        [Description("Gets or sets the words of the Row that are formatted.")]
        public WordCollection FormattedWords
        {
            get { return formattedWords; }
            set { formattedWords = value; }
        }

        /// <summary>
        /// Gets or sets the collection of words in the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Appearance")]
        [Description("Gets or sets the collection of words in the Row.")]
        public WordCollection Words
        {
            get { return words; }
            set { words = value; }
        }

        /// <summary>
        /// Gets or sets the current RowState of the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets the current RowState of the Row.")]
        public RowState RowState
        {
            get { return rowState; }
            set
            {
                if (value == rowState)
                    return;

                if (value == Objects.RowState.SegmentParsed && inKeywordQueue == false)
                {
                    document.KeywordQueue.Add(this);
                    inKeywordQueue = true;
                }

                if ((value == Objects.RowState.AllParsed || value == Objects.RowState.NotParsed) &&
                    inKeywordQueue == false)
                {
                    document.KeywordQueue.Remove(this);
                    inKeywordQueue = false;
                }

                rowState = value;
            }

        }

        /// <summary>
        /// Gets the 'depth' of the Row - basically the indentation level.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets the 'depth' of the Row - basically the indentation level.")]
        public int Depth
        {
            get
            {
                int indent = 0;
                Segment segment = startSegment;
                while (segment != null)
                {
                    if (segment.Scope != null && segment.Scope.CauseIndent)
                        indent++;

                    segment = segment.Parent;
                }

                if (this.ShouldOutdent == true)
                    indent--;

                return indent;
            }
        }

        /// <summary>
        /// Gets or sets whether the Row should be outdented.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets whether the Row should be outdented.")]
        public bool ShouldOutdent
        {
            get
            {
                if (startSegment.EndRow == this)
                {
                    if (startSegment.Scope.CauseIndent == true)
                        return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Gets or sets the background color of the Row.
        /// </summary>
        [Browsable(true)]
        [Category("Appearance")]
        [Description("Gets or sets the background color of the Row.")]
        public Color BackColor
        {
            get { return backColor; }
            set { backColor = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="Row"/> is hovered.
        /// </summary>
        /// <value><c>true</c> if hovered; otherwise, <c>false</c>.</value>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets or sets a value indicating whether this Row is hovered.")]
        public bool Hovered
        {
            get { return hovered; }
            set { hovered = value; }
        }

        /// <summary>
        /// Gets or sets whether the Row is bookmarked.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets whether the Row is bookmarked.")]
        public bool Bookmarked
        {
            get { return bookmarked; }
            set
            {
                bookmarked = value;

                if (value == true)
                    document.InvokeBookmarkAdded(this);
                else
                    document.InvokeBookmarkRemoved(this);

                document.InvokeChange();
            }
        }

        /// <summary>
        /// Gets or sets whether the Row has a breakpoint.
        /// </summary>
        [Browsable(false)]
        [Category("Behaviour")]
        [Description("Gets or sets whether the Row has a breakpoint.")]
        public bool Breakpoint
        {
            get { return breakpoint; }
            set
            {
                breakpoint = value;

                if (value == true)
                    document.InvokeBreakpointAdded(this);
                else
                    document.InvokeBreakpointRemoved(this);

                document.InvokeChange();
            }
        }

        /// <summary>
        /// Gets or sets the text of the Row.
        /// </summary>
        [Browsable(true)]
        [Category("Appearance")]
        [Description("Gets or sets the text of the Row.")]
        public string Text
        {
            get { return text; }
            set
            {
                bool parsePreview = false;
                if (text != value)
                {
                    parsePreview = true;
                    document.Modified = true;
                }

                text = value;
                if (document != null)
                {
                    if (parsePreview == true)
                    {
                        document.Parser.ParsePreviewLine(document.IndexOf(this));
                        document.OnApplyFormatRanges(this);
                    }

                    this.AddToParseQueue();
                }
            }
        }

        /// <summary>
        /// Gets the index of the first word in the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the index of the first word in the Row.")]
        public int StartWordIndex
        {
            get
            {
                if (expansionStartSegment == null)
                    return 0;

                Word word = expansionStartSegment.StartWord;

                int wordIndex = 0;
                foreach (Word currentWord in this)
                {
                    if (currentWord == word)
                        break;

                    wordIndex += currentWord.Text.Length;
                }

                return wordIndex;
            }
        }

        /// <summary>
        /// Gets the first non-whitespace word in the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the first non-whitespace word in the Row.")]
        public Word FirstNonWhitespaceWord
        {
            get
            {
                foreach (Word currentWord in this)
                {
                    if (currentWord.Type == WordType.Word)
                        return currentWord;
                }

                return null;
            }
        }

        /// <summary>
        /// Gets the word at the given index.
        /// </summary>
        /// <param name="index">Index of the word to find.</param>
        /// <returns>The found word.</returns>
        public Word this[int index]
        {
            get
            {
                if (index >= 0)
                    return words[index] as Word;
                else
                    return new Word();
            }
        }

        /// <summary>
        /// Gets the index of the Row in its parent document.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the index of the Row in its parent document.")]
        public int Index
        {
            get { return document.IndexOf(this); }
        }

        /// <summary>
        /// Gets the index of the Row if it is visible, else gets the first visible Row in the parent document.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the index of the Row if it is visible, else gets the first visible Row in the parent document.")]
        public int VisibleIndex
        {
            get
            {
                int rowIndex = document.VisibleRows.IndexOf(this);
                if (rowIndex == -1)
                {
                    if (startSegment != null)
                    {
                        if (startSegment.StartRow != null)
                        {
                            return startSegment.StartRow != this ? startSegment.StartRow.VisibleIndex : this.Index;
                        }
                        else
                            return this.Index;
                    }
                    else
                        return this.Index;
                }
                else
                    return rowIndex;
            }
        }

        /// <summary>
        /// Gets the next visible Row from this Row's index.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the next visible Row from this Row's index.")]
        public Row NextVisibleRow
        {
            get
            {
                int rowIndex = this.VisibleIndex;
                if (rowIndex > document.VisibleRows.Count)
                    return null;

                if (rowIndex + 1 < document.VisibleRows.Count)
                    return document.VisibleRows[rowIndex + 1];
                else
                    return null;
            }
        }

        /// <summary>
        /// Gets the next Row from this Row's index.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the next Row from this Row's index.")]
        public Row NextRow
        {
            get
            {
                int rowIndex = this.Index;
                if (rowIndex + 1 <= document.Lines.Length - 1)
                    return document[rowIndex + 1];
                else
                    return null;
            }
        }

        /// <summary>
        /// Gets the previous visible Row from this Row's index.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the previous visible Row from this Row's index.")]
        public Row PreviousVisibleRow
        {
            get
            {
                int rowIndex = this.VisibleIndex;
                if (rowIndex < 0)
                    return null;

                if (rowIndex - 1 >= 0)
                    return document.VisibleRows[rowIndex - 1];
                else
                    return null;
            }
        }

        /// <summary>
        /// Gets the previous Row from this Row's index.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the previous Row from this Row's index.")]
        public Row PreviousRow
        {
            get
            {
                int rowIndex = this.Index;

                if (rowIndex - 1 >= 0)
                    return document[rowIndex - 1];
                else
                    return null;
            }
        }

        /// <summary>
        /// Gets whether the Row is collapsed.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets whether the Row is collapsed.")]
        public bool IsCollapsed
        {
            get
            {
                if (expansionStartSegment != null)
                {
                    if (expansionStartSegment.Expanded == false)
                        return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Gets whether the ending part of the Row is collapsed.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets whether the ending part of the Row is collapsed.")]
        public bool IsCollapsedEndPart
        {
            get
            {
                if (expansionEndSegment != null)
                {
                    if (expansionEndSegment.Expanded == false)
                        return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Gets whether the Row can be folded.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets whether the Row can be folded.")]
        public bool CanFold
        {
            get { return (expansionStartSegment != null && expansionStartSegment.EndRow != null && document.IndexOf(expansionStartSegment.EndRow) != 0) && MayHaveFoldMarker == true; }
        }

        /// <summary>
        /// Gets whether the Row may be folded.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets whether the Row can be folded.")]
        internal bool MayHaveFoldMarker
        {
            get { return mayHaveFoldMarker; }
            set { mayHaveFoldMarker = value; }
        }

        /// <summary>
        /// Gets whether the ending part of the Row can fold.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets whether the ending part of the Row can fold.")]
        public bool CanFoldEndPart
        {
            get { return expansionEndSegment != null; }
        }

        /// <summary>
        /// Gets or sets whether the Row is expanded.
        /// </summary>
        [Browsable(false)]
        [Category("Appearance")]
        [Description("Gets or sets whether the Row is expanded.")]
        public bool Expanded
        {
            get
            {
                if (this.CanFold == true)
                    return expansionStartSegment.Expanded;
                else
                    return false;
            }
            set
            {
                if (this.CanFold == true)
                    expansionStartSegment.Expanded = value;
            }
        }

        /// <summary>
        /// Gets or sets the text of the Row when it is expanded.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [DefaultValue("")]
        [Description("Gets or sets the text of the Row when it is expanded.")]
        public string ExpansionText
        {
            get { return expansionStartSegment.Scope.ExpansionText; }
            set
            {
                Scope oldScope = expansionStartSegment.Scope;
                Scope newScope = new Scope();

                newScope.CaseSensitive = oldScope.CaseSensitive;
                newScope.CauseIndent = oldScope.CauseIndent;
                newScope.DefaultExpanded = oldScope.DefaultExpanded;
                newScope.EndPatterns = oldScope.EndPatterns;
                newScope.NormalizeCase = oldScope.NormalizeCase;

                newScope.Parent = oldScope.Parent;
                newScope.SpawnBlockOnEnd = oldScope.SpawnBlockOnEnd;
                newScope.SpawnBlockOnStart = oldScope.SpawnBlockOnStart;

                newScope.Start = oldScope.Start;
                newScope.Style = oldScope.Style;
                newScope.ExpansionText = value;

                expansionStartSegment.Scope = newScope;
                document.InvokeChange();
            }
        }

        /// <summary>
        /// Gets whether the Row has an expansion line.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets whether the Row has an expansion line.")]
        public bool HasExpansionLine
        {
            get { return endSegment.Parent != null; }
        }

        /// <summary>
        /// Gets the ending Row of the Row's expansion.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the ending Row of the Row's expansion.")]
        public Row ExpansionEndRow
        {
            get
            {
                if (this.CanFold == true)
                    return expansionStartSegment.EndRow;
                else
                    return this;
            }
        }

        /// <summary>
        /// Gets the starting Row of the Row's expansion.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the starting Row of the Row's expansion.")]
        public Row ExpansionStartRow
        {
            get
            {
                if (this.CanFoldEndPart == true)
                    return expansionEndSegment.StartRow;
                else
                    return this;
            }
        }

        /// <summary>
        /// Gets the Row as if it were collapsed.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the Row as if it were collapsed.")]
        public Row VirtualCollapsedRow
        {
            get
            {
                Row row = new Row();

                foreach (Word currentWord in this)
                {
                    if (expansionStartSegment == currentWord.Segment)
                        break;

                    row.Add(currentWord);
                }

                Word word = row.Add(this.CollapsedText);
                word.Style = new TextStyle();

                word.Style.BackColor = Color.Silver;
                word.Style.ForeColor = Color.DarkBlue;
                word.Style.Bold = true;

                bool found = false;
                if (this.ExpansionEndRow != null)
                {
                    foreach (Word currentWord in this.ExpansionEndRow)
                    {
                        if (found == true)
                            row.Add(currentWord);

                        if (currentWord == ExpansionEndRow.expansionEndSegment.EndWord)
                            found = true;
                    }
                }

                return row;
            }
        }

        /// <summary>
        /// Gets the text of the Row as if it were collapsed.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the text of the Row as if it were collapsed.")]
        public string CollapsedText
        {
            get
            {
                string subText = "";
                int position = 0;

                foreach (Word currentWord in this)
                {
                    position += currentWord.Text.Length;
                    if (currentWord.Segment == expansionStartSegment)
                    {
                        subText = Text.Substring(position).Trim();
                        break;
                    }
                }

                if (expansionStartSegment.Scope.ExpansionText != "")
                    subText = expansionStartSegment.Scope.ExpansionText.Replace("***", subText);

                return subText;
            }
        }

        /// <summary>
        /// Gets the number of words in the Row.
        /// </summary>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets the number of words in the Row.")]
        public int Count
        {
            get { return words.Count; }
        }

        #endregion

        #region Methods

        #region Public

        /// <summary>
        /// Clears the Row.
        /// </summary>
        public void Clear()
        {
            words = new WordCollection();
        }

        /// <summary>
        /// Ensures that this Row is visible in its parent document.
        /// </summary>
        public void EnsureVisible()
        {
            if (this.RowState == RowState.NotParsed)
                return;

            Segment segment = startSegment;
            while (segment != null)
            {
                segment.Expanded = true;
                segment = segment.Parent;
            }

            document.ResetVisibleRows();
        }

        /// <summary>
        /// Adds the given text as a Word to the Row.
        /// </summary>
        /// <param name="text">Text to add.</param>
        /// <returns>The added Word.</returns>
        public Word Add(string text)
        {
            Word word = new Word();

            word.Row = this;
            word.Text = text;

            words.Add(word);
            return word;
        }

        /// <summary>
        /// Inserts the specified text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="index">The index.</param>
        /// <returns>The inserted Word.</returns>
        public Word Insert(string text, int index)
        {
            Word word = new Word();

            word.Row = this;
            word.Text = text;

            words.Insert(index, word);
            return word;
        }

        /// <summary>
        /// Adds the Row to the parent document's parsing queue.
        /// </summary>
        public void AddToParseQueue()
        {
            if (inQueue == false)
                document.ParseQueue.Add(this);

            inQueue = true;
            this.RowState = RowState.NotParsed;
        }

        /// <summary>
        /// Sets the text of the Row.
        /// </summary>
        /// <param name="text">The text that should be the Row's text.</param>
        public void SetText(string text)
        {
            document.StartUndoCapture();

            TextPoint textPoint = new TextPoint(0, Index);
            TextRange textRange = new TextRange();

            textRange.FirstColumn = 0;
            textRange.FirstRow = textPoint.Y;
            textRange.LastColumn = Text.Length;
            textRange.LastRow = textPoint.Y;

            document.StartUndoCapture();

            document.PushUndoBlock(UndoAction.DeleteRange, document.GetRange(textRange), textRange.FirstColumn, textRange.FirstRow);
            document.PushUndoBlock(UndoAction.InsertRange, Text, textPoint.X, textPoint.Y);

            this.Text = text;
            document.EndUndoCapture();
            document.InvokeChange();
        }

        /// <summary>
        /// Updates the casing of all words in the Row.
        /// </summary>
        public void MatchCase()
        {
            StringBuilder builder = new StringBuilder();
            foreach (Word word in words)
            {
                if (word != null)
                    builder.Append(word.Text);
            }

            text = builder.ToString();
        }

        /// <summary>
        /// Returns the IEnumerator of the Row.
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            return words.GetEnumerator();
        }

        /// <summary>
        /// Parses the Row.
        /// </summary>
        public void Parse()
        {
            document.ParseRow(this);
        }

        /// <summary>
        /// Parses the Row.
        /// </summary>
        /// <param name="parseKeywords">Whether to parse keywords.</param>
        public void Parse(bool parseKeywords)
        {
            document.ParseRow(this, parseKeywords);
        }

        /// <summary>
        /// Sets the expansion Segment of the Row.
        /// </summary>
        public void SetExpansionSegment()
        {
            expansionStartSegment = null;
            expansionEndSegment = null;
            if (MayHaveFoldMarker == false)
            {
                if (StartSegment.StartRow == this && StartSegment.Parent != null)
                {
                    StartSegment.StartRow = StartSegment.Parent.StartRow;
                    StartSegment.StartWord = StartSegment.Parent.StartWord;
                }
                else
                {
                    StartSegment.EndRow = EndSegment.EndRow;
                }

                if (EndSegment.StartRow == this && EndSegment.Parent != null)
                {
                    EndSegment.StartRow = EndSegment.Parent.StartRow;
                    EndSegment.StartWord = EndSegment.Parent.StartWord;
                }

                startSegments.Clear();
                endSegments.Clear();
                return;
            }

            foreach (Segment segment in startSegments)
            {
                if (endSegments.Contains(segment) == false)
                {
                    expansionStartSegment = segment;
                    break;
                }
            }

            foreach (Segment segment in endSegments)
            {
                if (startSegments.Contains(segment) == false)
                {
                    expansionEndSegment = segment;
                    break;
                }
            }

            if (expansionEndSegment != null)
                expansionStartSegment = null;
        }

        /// <summary>
        /// Returns the leading whitespace of the Row's text.
        /// </summary>
        /// <returns>The leading whitespace of the Row's text.</returns>
        public string GetLeadingWhitespace()
        {
            string rowText = text;
            int index = 0;
            rowText = rowText.Replace("	", " ");
            for (index = 0; index < rowText.Length; index++)
            {
                if (rowText.Substring(index, 1) != " ")
                    break;
            }

            return text.Substring(0, index);
        }

        /// <summary>
        /// Returns the leading virtual whitespace.
        /// </summary>
        /// <returns>The leading virtual whitespace.</returns>
        public string GetVirtualLeadingWhitespace()
        {
            int index = this.StartWordIndex;
            string whitespace = "";
            foreach (char c in this.Text)
            {
                if (c == '\t')
                    whitespace += c;
                else
                    whitespace += ' ';

                index--;
                if (index <= 0)
                    break;
            }

            return whitespace;
        }

        /// <summary>
        /// Adds the given word to the Row.
        /// </summary>
        /// <param name="word">Row to add.</param>
        public void Add(Word word)
        {
            words.Add(word);
        }

        /// <summary>
        /// Returns the index of the given word.
        /// </summary>
        /// <param name="word">Word to found index of.</param>
        /// <returns>The index of the given word.</returns>
        public int IndexOf(Word word)
        {
            return words.IndexOf(word);
        }

        /// <summary>
        /// Finds the next word to the right from the start word that has the given PatternList as parent.
        /// </summary>
        /// <param name="patternList">Parent PatternList.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindRightWordByPatternList(PatternList patternList, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index++;

            Word word = null;
            while (index < words.Count)
            {
                word = this[index];
                if (word.Pattern != null)
                {
                    if (word.Pattern.Parent != null)
                    {
                        if (word.Pattern.Parent == patternList && word.Type != WordType.Space && word.Type != WordType.Tab)
                            return word;
                    }
                }

                index++;
            }

            return null;
        }

        /// <summary>
        /// Finds the next word to the right from the start word that has the given PatternList found from the given name as parent.
        /// </summary>
        /// <param name="patternListName">Name of the parent PatternList.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindRightWordByPatternListName(string patternListName, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index++;

            Word word = null;
            while (index < words.Count)
            {
                word = this[index];
                if (word.Pattern != null)
                {
                    if (word.Pattern.Parent != null)
                    {
                        if (word.Pattern.Parent.Name == patternListName && word.Type != WordType.Space && word.Type != WordType.Tab)
                            return word;
                    }
                }

                index++;
            }

            return null;
        }

        /// <summary>
        /// Finds the next word to the left from the start word that has the given PatternList as parent.
        /// </summary>
        /// <param name="patternList">Parent PatternList.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindLeftWordByPatternList(PatternList patternList, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index--;

            Word word = null;
            while (index >= 0)
            {
                word = this[index];
                if (word.Pattern != null)
                {
                    if (word.Pattern.Parent != null)
                    {
                        if (word.Pattern.Parent == patternList && word.Type != WordType.Space && word.Type != WordType.Tab)
                            return word;
                    }
                }

                index--;
            }

            return null;
        }

        /// <summary>
        /// Finds the next word to the left from the start word that has the given PatternList found from the given name as parent.
        /// </summary>
        /// <param name="patternListName">Name of the parent PatternList.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindLeftWordByPatternListName(string patternListName, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index--;

            Word word = null;
            while (index >= 0)
            {
                word = this[index];
                if (word.Pattern != null)
                {
                    if (word.Pattern.Parent != null)
                    {
                        if (word.Pattern.Parent.Name == patternListName && word.Type != WordType.Space && word.Type != WordType.Tab)
                            return word;
                    }
                }

                index--;
            }

            return null;
        }

        /// <summary>
        /// Finds the next word to the right from the start word that has the given Block as parent.
        /// </summary>
        /// <param name="block">Parent Block.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindRightWordByBlock(Block block, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index++;

            Word word = null;
            while (index < words.Count)
            {
                word = this[index];
                if (word.Segment.Block == block && word.Type != WordType.Space && word.Type != WordType.Tab)
                    return word;

                index++;
            }

            return null;
        }

        /// <summary>
        /// Finds the next word to the right from the start word that has the given Block found from the given name as parent.
        /// </summary>
        /// <param name="blockName">Name of the parent Block.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindRightWordByBlockName(string blockName, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index++;

            Word word = null;
            while (index < words.Count)
            {
                word = this[index];
                if (word.Segment.Block.Name == blockName && word.Type != WordType.Space && word.Type != WordType.Tab)
                    return word;

                index++;
            }

            return null;
        }

        /// <summary>
        /// Finds the next word to the left from the start word that has the given Block as parent.
        /// </summary>
        /// <param name="block">Parent Block.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindLeftWordByBlock(Block block, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index--;

            Word word = null;
            while (index >= 0)
            {
                word = this[index];
                if (word.Segment.Block == block && word.Type != WordType.Space && word.Type != WordType.Tab)
                    return word;

                index--;
            }

            return null;
        }

        /// <summary>
        /// Finds the next word to the left from the start word that has the given Block found from the given name as parent.
        /// </summary>
        /// <param name="blockName">Name of the parent Block.</param>
        /// <param name="startWord">Starting word.</param>
        /// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
        /// <returns>The found word.</returns>
        public Word FindLeftWordByBlockName(string blockName, Word startWord, bool ignoreStartWord)
        {
            int index = startWord.Index;
            if (ignoreStartWord == true)
                index--;

            Word word = null;
            while (index >= 0)
            {
                word = this[index];
                if (word.Segment.Block.Name == blockName && word.Type != WordType.Space && word.Type != WordType.Tab)
                    return word;

                index--;
            }

            return null;
        }

        #endregion

        #endregion

        /// <summary>
        /// Initializes a new instance of Row.
        /// </summary>
        public Row()
        {
        }
    }
}
